﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MetaModelProperty.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the MetaModelProperty type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.ComponentModel.Modelling
{
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Reflection;
    using Collections;

    public class MetaModelProperty : MetaModelMember
    {
        #region Fields

        private ReadOnlyCollection<PropertyChangingAttribute> _changingAttributes;
        private ReadOnlyCollection<ValidationAttribute> _validationAttributes;

        #endregion

        internal MetaModelProperty(MetaModelType type, PropertyInfo property, int index)
        {
            Type = type;
            ReflectedProperty = property;
            Index = index;
            PropertyChangedEventArgs = new PropertyChangedEventArgs(Name);
            _validationAttributes = Attributes.OfType<ValidationAttribute>().ToList().AsReadOnly();
        }

        /// <summary>
        /// Gets the <see cref="MetaModelType"/> that defines the property.
        /// </summary>
        /// <value>The type.</value>
        public MetaModelType Type { get; private set; }

        public override MemberInfo ReflectedMember
        {
            get { return ReflectedProperty; }
        }

        public PropertyInfo ReflectedProperty { get; private set; }

        public int Index { get; private set; }

        public override MetaModel Model
        {
            get { return Type.Model; }
        }

        protected ReadOnlyCollection<ValidationAttribute> ValidationAttributes
        {
            get { return _validationAttributes ?? (_validationAttributes = Attributes.OfType<ValidationAttribute>().ToList().AsReadOnly()); }
        }

        private ReadOnlyCollection<PropertyChangingAttribute> ChangingAttributes
        {
            get
            {
                return _changingAttributes ?? (_changingAttributes =
                    Type.Attributes.OfType<PropertyChangingAttribute>()
                            .Select((attribute, index) => new AttributeSortInfo(attribute, 0, index))
                            .Union(Attributes.OfType<PropertyChangingAttribute>()
                                .Select((attribute, index) => new AttributeSortInfo(attribute, 1, index)))
                            .OrderBy(x => x.Level)
                            .ThenBy(x => x.Priority)
                            .ThenBy(x => x.Index)
                            .Select(x => x.Attribute)
                            .ToList()
                            .AsReadOnly());
            }
        }

        public bool HasValidation
        {
            get { return ValidationAttributes.Count > 0; }
        }

        /// <summary>
        /// Gets a <see cref="PropertyChangedEventArgs"/> instance for the property.
        /// </summary>
        /// <value>The property changed event args.</value>
        public PropertyChangedEventArgs PropertyChangedEventArgs { get; private set; }

        internal bool OnChanging<T>(object instance, T oldValue, ref T newValue)
        {
            if (ChangingAttributes.Count == 0)
            {
                return true;
            }

            var returnValue = (object)newValue;
            if (!OnChanging(ChangingAttributes, instance, oldValue, ref returnValue))
            {
                newValue = oldValue;
                return false;
            }

            newValue = (T)returnValue;
            return !EqualityComparer<T>.Default.Equals(oldValue, newValue);
        }

        internal void OnValidating(object value, ValidationContext validationContext, Collection<ValidationResult> results)
        {
            Validator.TryValidateValue(value, validationContext, results, ValidationAttributes);
        }

        private bool OnChanging(IEnumerable<PropertyChangingAttribute> attributes, object instance, 
            object oldValue, ref object newValue)
        {
            foreach (var behavior in attributes)
            {
                if (!behavior.OnChanging(instance, this, oldValue, ref newValue))
                {
                    return false;
                }
            }

            return true;
        }

        #region Nested type: AttributeSortInfo

        private class AttributeSortInfo
        {
            public AttributeSortInfo(PropertyChangingAttribute attribute, int level, int index)
            {
                Attribute = attribute;
                Level = level;
                Index = index;
            }

            public PropertyChangingAttribute Attribute { get; private set; }

            public int Level { get; private set; }

            public int Index { get; private set; }

            public int Priority
            {
                get { return Attribute.Priority; }
            }
        }

        #endregion
    }

    
}